/*******************************************************************************
 * Copyright (c) 2009 Ravenhearte Design.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 	*	Redistributions of source code must retain the above copyright
 * 		notice, this list of conditions and the following disclaimer.
 * 
 * 	*	Redistributions in binary form must reproduce the above copyright
 * 		notice, this list of conditions and the following disclaimer in the
 * 		documentation and/or other materials provided with the distribution.
 * 
 * 	*	Neither the name of 'Ravenhearte Design' nor the names of its contributors 
 * 		may be used to endorse or promote products derived from this software 
 * 		without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/

package org.ravenhearte.engine;

import org.ravenhearte.engine.input.EventHandler;
import org.ravenhearte.engine.input.InputEvent;
import org.ravenhearte.engine.input.InputEventManager;
import org.ravenhearte.engine.input.InputEvent.EventType;
import org.ravenhearte.engine.input.InputEventManager.Queue;

import com.jme.input.KeyInput;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;

public class SphericalBoundCamera implements EventHandler {
	Camera camera;
	Vector3f sphericalCoords, cameraAnchor, cameraFocus, cameraLoc;
	boolean forward, backward, left, right, rightClick;

	public SphericalBoundCamera(Camera cam, Vector3f start, Vector3f offsets) {
		forward = backward = left = right = rightClick = false;
		camera = cam;
		cameraAnchor = cameraFocus = start;

		sphericalCoords = new Vector3f();

		cartesianToSpherical(offsets);
		sphericalToWorld();

		camera.setLocation(cameraLoc);
		camera.lookAt(cameraAnchor, new Vector3f(0, 0, 1));

		loadEvents();
	}

	public void update(float tpf) {
		Vector3f direction = camera.getDirection();
		Vector3f leftward = camera.getLeft();

		direction.z = 0;

		if (left) {
			cameraFocus = cameraFocus.add(leftward.normalize().mult(tpf * 5f));
		}
		if (right) {
			cameraFocus = cameraFocus.subtract(leftward.normalize().mult(
					tpf * 5f));
		}
		if (forward) {
			cameraFocus = cameraFocus.add(direction.normalize().mult(tpf * 5f));
		}
		if (backward) {
			cameraFocus = cameraFocus.subtract(direction.normalize().mult(
					tpf * 5f));
		}

		if (cameraAnchor.distance(cameraFocus) < .1) {
			cameraAnchor = cameraFocus;
		} else {
			Vector3f moveDirection = cameraFocus.subtract(cameraAnchor);
			moveDirection.normalizeLocal();
			cameraAnchor.addLocal(moveDirection.mult(tpf * 5));
		}

		sphericalToWorld();
		camera.setLocation(cameraLoc);
		camera.lookAt(cameraAnchor, new Vector3f(0, 0, 1));
		camera.update();

		if (left || right || forward || backward) {
			InputEvent evnt = new InputEvent();
			evnt.parent = this;
			evnt.type = EventType.CUSTOM;
			evnt.textId = "CameraChange";
			InputEventManager.eM.generateEvent(evnt);
		}
	}

	private void cartesianToSpherical(Vector3f offsets) {
		sphericalCoords.x = FastMath.sqrt(FastMath.sqr(offsets.x)
				+ FastMath.sqr(offsets.y) + FastMath.sqr(offsets.z));
		Float s = FastMath.sqrt(FastMath.sqr(offsets.x)
				+ FastMath.sqr(offsets.y));
		sphericalCoords.y = FastMath.acos(offsets.z / sphericalCoords.x);
		if (offsets.x <= 0) {
			sphericalCoords.z = FastMath.PI - FastMath.asin(offsets.y / s);
		} else {
			sphericalCoords.z = FastMath.asin(offsets.y / s);
		}
	}

	private void sphericalToWorld() {
		cameraLoc = new Vector3f(0, 0, 0);

		cameraLoc.x = sphericalCoords.x * FastMath.sin(sphericalCoords.y)
				* FastMath.cos(sphericalCoords.z) + cameraAnchor.x;
		cameraLoc.y = sphericalCoords.x * FastMath.sin(sphericalCoords.y)
				* FastMath.sin(sphericalCoords.z) + cameraAnchor.y;
		cameraLoc.z = sphericalCoords.x * FastMath.cos(sphericalCoords.y)
				+ cameraAnchor.z;
	}

	private void loadEvents() {
		InputEvent evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_UP;
		evnt.type = EventType.KEYDOWN;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_UP;
		evnt.type = EventType.KEYUP;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_DOWN;
		evnt.type = EventType.KEYDOWN;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_DOWN;
		evnt.type = EventType.KEYUP;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_LEFT;
		evnt.type = EventType.KEYDOWN;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_LEFT;
		evnt.type = EventType.KEYUP;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_RIGHT;
		evnt.type = EventType.KEYDOWN;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.id = KeyInput.KEY_RIGHT;
		evnt.type = EventType.KEYUP;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.type = EventType.BUTTONDOWN;
		evnt.id = 1;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.type = EventType.BUTTONUP;
		evnt.id = 1;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);
		
		evnt = new InputEvent();
		evnt.parent = this;
		evnt.type = EventType.MOVE;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);

		evnt = new InputEvent();
		evnt.parent = this;
		evnt.type = EventType.WHEEL;
		InputEventManager.eM.registerEvent(Queue.WORLD, evnt);
	}
	
	public void unloadEvents() {
		InputEventManager.eM.unregisterEvents(this);
	}

	@Override
	public boolean onEvent(InputEvent ev) {
		switch (ev.type) {
		case KEYDOWN:
			switch (ev.id) {
			case KeyInput.KEY_UP:
				forward = true;
				return true;
			case KeyInput.KEY_DOWN:
				backward = true;
				return true;
			case KeyInput.KEY_LEFT:
				left = true;
				return true;
			case KeyInput.KEY_RIGHT:
				right = true;
				return true;
			}
			break;
		case KEYUP:
			switch (ev.id) {
			case KeyInput.KEY_UP:
				forward = false;
				return true;
			case KeyInput.KEY_DOWN:
				backward = false;
				return true;
			case KeyInput.KEY_LEFT:
				left = false;
				return true;
			case KeyInput.KEY_RIGHT:
				right = false;
				return true;
			}
			break;
		case BUTTONDOWN:
			rightClick = true;
			return true;
		case BUTTONUP:
			rightClick = false;
			return true;
		case MOVE:
			if (rightClick) {
				float phi = sphericalCoords.y + (ev.delta2 / 200f);
				float theta = sphericalCoords.z - (ev.delta / 200f);

				if (phi < .1f) {
					phi = .1f;
				} else if (phi > (FastMath.PI / 2f - .1f)) {
					phi = (FastMath.PI / 2f - .1f);
				}
				sphericalCoords.y = phi;
				sphericalCoords.z = theta;

				InputEvent evnt = new InputEvent();
				evnt.parent = this;
				evnt.type = EventType.CUSTOM;
				evnt.textId = "CameraChange";
				InputEventManager.eM.generateEvent(evnt);

				return false;
			}
			break;
		case WHEEL:
			if (KeyInput.get().isKeyDown(KeyInput.KEY_LSHIFT)) {
				ev.delta *= 20;
			}

			float rho = sphericalCoords.x - (ev.delta / 100f);

			if (rho < 10) {
				rho = 10;
			}

			sphericalCoords.x = rho;
			return true;
		}

		return false;
	}
}
